message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

include("${ISLAND_BASE_DIR}/island_globals_utils.cmake")

# Hot-reloading is enabled by default for debug target,
# whilst release target are built as statically linked 
# binaries by default. 
# This can be changed on a project-by-project basis, as
# we only set initial default settings here.

if( CMAKE_BUILD_TYPE STREQUAL "Debug" )
    # we enable hot-reloading by default for debug target
    set(PLUGINS_DYNAMIC ON CACHE BOOL "Use dynamic linking for all plugins")
else()
    # we disdable hot-reloading by default for release target
    set(PLUGINS_DYNAMIC OFF CACHE BOOL "Use dynamic linking for all plugins")
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    # manually set that we want to use libc pthread - there seems to be an
    # issue with detecting Thread support, otherwise.
    set (CMAKE_HAVE_LIBC_PTHREAD TRUE)
    # use lld linker for clang-compiled binary - it's faster than gnu ld,
    # and you don't have to worry about the linking order for libraries.
    set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-fuse-ld=lld -Wno-unused-command-line-argument")
    set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-fuse-ld=lld -Wno-unused-command-line-argument")
endif()

set ( STATIC_LIBRARIES "" CACHE INTERNAL "static_libraries" )
set ( DEPENDENCY_DEPTH 0 CACHE INTERNAL "current depth in dependency hierarchy" )

# We want position independent code by default (-fPIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

set(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Suffix for debug builds" FORCE)

set ( PLUGIN_LIBS_DEPENDENCIES "" CACHE INTERNAL "plugins_libs_dependencies" )
set ( PLUGIN_LINK_DIRS "")
    
set ( CMAKE_DEBUG_POSTFIX "" CACHE STRING "Suffix for debug builds" FORCE )

# specify output directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"  CACHE PATH "" FORCE )
set(CMAKE_MODULE_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/modules" CACHE PATH "" FORCE)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/modules" CACHE PATH "archive output directory" FORCE)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/modules" CACHE PATH "" FORCE )

# get git commit version for island framework
execute_process(COMMAND
  git describe --match=NeVeRmAtCh --always --abbrev=40 --dirty
  WORKING_DIRECTORY "${ISLAND_BASE_DIR}"
  OUTPUT_VARIABLE ISLAND_GIT_SHA1
  ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)


function (link_resources param_target param_link_name)
    # create a link to local resources
    if (WIN32)
        if (EXISTS "${param_link_name}")
            message(STATUS "local resources exists ${param_link_name}")
        else()    
            get_filename_component(real_path "${param_link_name}" REALPATH)
            string(REPLACE "/" "\\" target_path "${real_path}")

            get_filename_component(real_path "${param_target}" REALPATH)
            string(REPLACE "/" "\\" src_path "${real_path}")

            execute_process(COMMAND cmd /C mklink /J "${target_path}" "${src_path}" 
                RESULT_VARIABLE link_result 
                OUTPUT_VARIABLE link_output)
            message(STATUS "created symbolic link ${target_path} ${src_path} ${link_output} ${link_result}")
        endif()    
        
    else()
        if (EXISTS "${param_link_name}")
            message(STATUS "Link already exists: '${param_link_name}'")
        else()
            get_filename_component( tmp_link_dir "${param_link_name}" DIRECTORY )
            execute_process(COMMAND mkdir -p "${tmp_link_dir}" )
            execute_process(COMMAND ln -sfn "${param_target}" "${param_link_name}" )
            message(STATUS "Created symbolic link '${param_link_name}' → '${param_target}'" )
        endif()
    endif()

endfunction(link_resources)

# Adds a directory to the modules locations
macro(add_island_module_location MODULE_LOCATION)
    set( MODULE_LOCATIONS_LIST ${MODULE_LOCATIONS_LIST} ${MODULE_LOCATION} CACHE INTERNAL "module_locations_list" )
    # include_directories( "${MODULE_LOCATIONS_LIST}")
endmacro()

function(include_using_absolute_path TMP_INCLUDE_PATH)
    get_filename_component(TMP_INCLUDE_PATH_ABS ${TMP_INCLUDE_PATH} REALPATH BASE_DIR ${ISLAND_BASE_DIR})
    include_directories(${TMP_INCLUDE_PATH_ABS})
endfunction()

# include the island base directory
include_using_absolute_path("${ISLAND_BASE_DIR}")

include_using_absolute_path("${ISLAND_BASE_DIR}/modules/le_core") 

# We will store all possible module locations in this list, if the same module exists in multiple locations, the last one will be used
set ( MODULE_LOCATIONS_LIST CACHE INTERNAL "module_locations_list")

# Set default island module location
add_island_module_location("${ISLAND_BASE_DIR}/modules")

# We will store all loaded module names in this global list, so that we can make sure that modules don't get loaded more than once.
set_global_var( GLOBAL_LOADED_MODULES_LIST "" )

# We will store all requested module names in this global list, to keep track of all modules which have been requested.
# once this list is empty we know there are no more modules which need to be loaded.
set_global_var( GLOBAL_REQUESTED_MODULES_LIST "")


# These modules are always loaded - they control the plugin system.
set ( ISLAND_LOADER_MODULES "le_log;le_file_watcher;le_core" )

# These modules from the renderer - if you want to draw graphics, you want these.
# Note that order matters: dependent modules must be named before their dependencies.
set ( CORE_ISLAND_MODULES "le_pipeline_builder;le_window;le_backend_vk;le_renderer;le_swapchain_vk;le_jobs;le_shader_compiler")


if (REQUIRES_ISLAND_CORE)

    # add glm include directory to includes search path 
    include_using_absolute_path("${ISLAND_BASE_DIR}/3rdparty/src/glm/")

    add_compile_definitions( GLM_ENABLE_EXPERIMENTAL )
    add_compile_definitions( GLM_FORCE_DEPTH_ZERO_TO_ONE) #  vulkan clip space is from 0 to 1
    add_compile_definitions( GLM_FORCE_RIGHT_HANDED )     #  glTF uses right handed coordinate system, and we're following its lead.


    # create a link to project specific resources in runtime output directory
    if (EXISTS ${PROJECT_SOURCE_DIR}/resources)
        link_resources("${PROJECT_SOURCE_DIR}/resources" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/local_resources")
    endif()

    # create a link to shared resources
    link_resources(${ISLAND_BASE_DIR}/resources ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources )
    
    # Check if a local copy of the Vulkan SDK exists, and the environment VULKAN_SDK points to it.
    # If yes, use this directory for header includes, and linking.
    #
    set (VULKAN_SDK_ENV $ENV{VULKAN_SDK})
    if (EXISTS ${VULKAN_SDK_ENV})
        #include vulkan from SDK installation directory
        include_directories(${VULKAN_SDK_ENV}/include/)
        # NOTE: We're linking Vulkan from this system's Vulkan SDK directory
        link_directories(${VULKAN_SDK_ENV}/lib)
    endif(EXISTS ${VULKAN_SDK_ENV})

endif()


# Add required modules to modules list based on user flags
#
if (REQUIRES_ISLAND_LOADER)
    append_to_global_var(GLOBAL_REQUESTED_MODULES_LIST "${ISLAND_LOADER_MODULES}")
endif()
 
if (REQUIRES_ISLAND_CORE)
    append_to_global_var(GLOBAL_REQUESTED_MODULES_LIST "${CORE_ISLAND_MODULES}")
endif()

# ----------------------------------------------------------------------

macro(add_static_lib LIB_NAME)

    if (NOT ${LIB_NAME} IN_LIST STATIC_LIBRARIES})
        set ( STATIC_LIBRARIES ${LIB_NAME} $CACHE{STATIC_LIBRARIES} CACHE INTERNAL "static_libraries" )
        # message( STATUS "${TMP_INDENT}>>> Static libs: ${STATIC_LIBRARIES}")
    else()
        # set (STATIC_LIBRARIES ${STATIC_LIBRARIES} PARENT_SCOPE)
        message( STATUS "[ NOTE ] Rejecting extra static lib addition: `${LIB_NAME}` - lib already present."  )
    endif()

endmacro(add_static_lib LIB_NAME)

# ----------------------------------------------------------------------

macro(print_current_includes)
    message(STATUS "${TMP_INDENT}includes for ${CMAKE_CURRENT_SOURCE_DIR}:")
    get_directory_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} INCLUDE_DIRECTORIES)
    foreach(dir ${dirs})
        message(STATUS "${TMP_INDENT}include='${dir}'")
    endforeach()
    message(STATUS "${TMP_INDENT}\n")
endmacro()


# ----------------------------------------------------------------------
# find module - set MODULE_LOCATION in the function's parent scope to
# any found location. MODULE_LOCATION is set to "" if nothing is found.
function(find_module MODULE_LOCATION MODULE_NAME )
    # look for the module in all given locations
    set( MODULE_LOCATION "" PARENT_SCOPE)
    foreach( LOCATION ${MODULE_LOCATIONS_LIST} )
        set( CURRENT_LOCATION "${LOCATION}/${MODULE_NAME}" )
        if( EXISTS ${CURRENT_LOCATION} )
            get_filename_component(CURRENT_LOCATION ${CURRENT_LOCATION} REALPATH BASE_DIR ${ISLAND_BASE_DIR})
            set( MODULE_LOCATION ${CURRENT_LOCATION} PARENT_SCOPE)
        endif()
    endforeach()
endfunction()

# ----------------------------------------------------------------------
# Loads a requested module - note that this may recursively 
# trigger loading more modules, as modules are added via 
# add_subdirectory, which evaluates their cmake.txt file.
macro(include_island_module MODULE_NAME SHOULD_ADD)

    find_module(MODULE_LOCATION ${MODULE_NAME} ) # sets MODULE_LOCATION
    
    # message(STATUS "${TMP_INDENT}Module location : ${MODULE_LOCATION}")

    if( MODULE_LOCATION ) 
        set ( TMP_INDENT "")
        string(REPEAT "  " ${DEPENDENCY_DEPTH} TMP_INDENT)


        get_directory_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} INCLUDE_DIRECTORIES)

        if (NOT ${MODULE_LOCATION} IN_LIST dirs)
            # message(STATUS "${TMP_INDENT}Loading module  : ${MODULE_NAME}")
            include_directories( "${MODULE_LOCATION}")
            # message(STATUS "${TMP_INDENT}+++ Adding include : ${MODULE_LOCATION}")
        endif()

        # set_directory_properties()
        if ( ${SHOULD_ADD} )
            message(STATUS "${TMP_INDENT} ${MODULE_NAME}")
            add_subdirectory( ${MODULE_LOCATION} ${MODULE_NAME} )
        endif()

        # print_current_includes()

    else()
        message( SEND_ERROR "Module not found  : ${MODULE_NAME}")
    endif()

endmacro()

# ----------------------------------------------------------------------
# Call this macro from other modules to establish a dependency.
# Adds a module name to list of requested modules, 
# checks whether a module was already requested to prevent duplicates.
macro(request_island_module MODULE_NAME)

	get_global_var(GLOBAL_LOADED_MODULES_LIST LOADED_MODULES_LIST)

    if (NOT ${MODULE_NAME} IN_LIST LOADED_MODULES_LIST)
        # prepend module name to loaded_modules_list in global scope
        append_to_global_var(GLOBAL_REQUESTED_MODULES_LIST ${MODULE_NAME})
        include_island_module( ${MODULE_NAME} FALSE )
    else()
        include_island_module( ${MODULE_NAME} FALSE )
        # message(STATUS "[ NOTE ] Rejecting add_island_module request: `${MODULE_NAME}` - Module already present."  )
    endif()

endmacro(request_island_module)

# ----------------------------------------------------------------------
# Call this macro from other modules to establish a dependency.
# Adds a module name to list of requested modules, 
# checks whether a module was already requested to prevent duplicates.
macro(load_island_module MODULE_NAME)

	get_global_var(GLOBAL_LOADED_MODULES_LIST LOADED_MODULES_LIST)
    
    if (NOT ${MODULE_NAME} IN_LIST "${LOADED_MODULES_LIST}")
        # prepend module name to loaded_modules_list in global scope
		append_to_global_var(GLOBAL_LOADED_MODULES_LIST "${MODULE_NAME}")
        include_island_module( ${MODULE_NAME} TRUE )
    else()
        include_island_module( ${MODULE_NAME} FALSE )
        message(STATUS "[ NOTE ] Rejecting load_island_module request: `${MODULE_NAME}` - Module already present."  )
    endif()

endmacro(load_island_module)

# ----------------------------------------------------------------------

macro (depends_on_island_module MODULE_NAME)
    math( EXPR TMP_DEPENDENCY_DEPTH "$CACHE{DEPENDENCY_DEPTH}+1")
    set ( DEPENDENCY_DEPTH ${TMP_DEPENDENCY_DEPTH} CACHE INTERNAL "current depth in dependency hierarchy" )
    
    set ( TMP_INDENT "")
    string(REPEAT "  " ${DEPENDENCY_DEPTH} TMP_INDENT)

    get_filename_component(THIS_MODULE_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME_WE )
    message(STATUS "${TMP_INDENT} ${THIS_MODULE_NAME} ← ${MODULE_NAME}")

    request_island_module(${MODULE_NAME})

    math( EXPR TMP_DEPENDENCY_DEPTH "$CACHE{DEPENDENCY_DEPTH}-1")
    set ( DEPENDENCY_DEPTH ${TMP_DEPENDENCY_DEPTH} CACHE INTERNAL "current depth in dependency hierarchy" )
endmacro(depends_on_island_module)


# ----------------------------------------------------------------------

macro(add_dynamic_linker_flags)
    
    if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        
        # IMPORTANT: --no-gnu-unique for gcc compiler
        #
        # This compiler flag is neccesary as otherwise the library may get compiled
        # with some symbols exported as UNIQUE, which implicitly makes this library
        # un-closeable. This means, any calls to `dlclose` on this library, once
        # loaded, will have no effect, and autoreload for this library will not work
        # as the first version of the library will remain resident.
        
        target_compile_options (${TARGET} PUBLIC --no-gnu-unique)
        
    endif()

    if (WIN32 AND NOT ${TARGET} MATCHES "le_core")

        # On windows, when hot-reloading, we must make sure we can actually write 
        # our artifacts even if the program is running. To be sure, we attempt to
        # first move any old artifacts which may still be in use by renaming them.
        # 
        set(TARGET_ARTIFACT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}")
        
        add_custom_command(TARGET ${TARGET} 
                           PRE_LINK
                           COMMAND ${CMAKE_COMMAND}
                               -DFileToCheck=${TARGET_ARTIFACT}.dll
                               -DMethod=Rename
                               -P ${ISLAND_BASE_DIR}/CMakeLists.txt.rename_artifact.in
                           COMMENT "Checking if ${TARGET_ARTIFACT}.dll exists...")

        add_custom_command(TARGET ${TARGET} 
                           PRE_LINK
                           COMMAND ${CMAKE_COMMAND}
                               -DFileToCheck=${TARGET_ARTIFACT}.pdb
                               -DMethod=Rename
                               -P ${ISLAND_BASE_DIR}/CMakeLists.txt.rename_artifact.in
                           COMMENT "Checking if ${TARGET_ARTIFACT}.pdb exists...")

        # To signal to the hot-reloading file watcher that a re-compile has occured,
        # we touch a flag file which shares the same basename as the artifact, and 
        # has the file ending ".flag" - We want to update this only at the very end 
        # of the build process to make sure that all file writing operations have 
        # completed - if we watch the .dll directly there is a chance that the hot-
        # reloading mechanism gets triggered mutliple times because the .dll may get
        # accessed a couple of times during the build process.
        # 
        # by using a flag file which we touch only once at the end of the build we
        # whould have a fairly atomic marker by which to tell whether the build 
        # process has truly completed.
        add_custom_command(TARGET ${TARGET} 
                           POST_BUILD
                           COMMAND ${CMAKE_COMMAND}
                               -DFileToCheck=${TARGET_ARTIFACT}.flag
                               -DMethod=Touch
                               -P ${ISLAND_BASE_DIR}/CMakeLists.txt.rename_artifact.in
                           COMMENT "Touchting ${TARGET_ARTIFACT}.flag")

        # additionally, we don't want incremental linking, because that will 
        # fail anyway, for it won't be able to find the last version of the .dll, 
        # since we've just removed it.
        set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "/INCREMENTAL:NO")

        # on windows we must link all libaries with le_core so that they can find 
        # le_core's exported functions which are used for loading module apis.

        target_link_libraries(${TARGET} PRIVATE "le_core" )

    endif()

    
endmacro(add_dynamic_linker_flags)

# ----------------------------------------------------------------------

if(${MODULE_NAME})
  append_to_global_var(GLOBAL_REQUESTED_MODULES_LIST "${MODULE_NAME}")
endif()

get_global_var(GLOBAL_REQUESTED_MODULES_LIST REQUESTED_MODULES)
message(STATUS "----- requested modules: ${REQUESTED_MODULES}")


